Skip to content

docs: onchain randomness guide#62

Merged
marc0olo merged 3 commits into
mainfrom
docs/guides-backends-randomness
Apr 16, 2026
Merged

docs: onchain randomness guide#62
marc0olo merged 3 commits into
mainfrom
docs/guides-backends-randomness

Conversation

@marc0olo
Copy link
Copy Markdown
Member

@marc0olo marc0olo commented Apr 16, 2026

Summary

  • Explains why blockchain randomness is hard (deterministic execution) and how ICP's threshold VRF solves it
  • Covers the raw_rand management canister API: caller constraints (update-only), 32-byte return, cycles cost
  • Shows Motoko implementation using mo:core/RandomRandom.blob() + Blob.toArray with direct array indexing for entropy consumption
  • Shows Rust implementation using ic_cdk::management_canister::raw_rand() with slice-based multi-value extraction
  • Demonstrates range generation, winner selection from a list, and seeding rand::StdRng (getrandom integration)
  • Security section: update-only constraint, batch entropy reuse, timing guarantees, reentrancy caution at await
  • Links to random_maze Motoko example for a full walkthrough (notes it predates mo:core and uses mo:base)

Sync recommendation

informed by dfinity/portaldocs/building-apps/integrations/randomness.mdx; informed by dfinity/cdk-rs and caffeinelabs/motoko-core; no dedicated icskill exists for randomness/VRF

@marc0olo
Copy link
Copy Markdown
Member Author

Review: Randomness

Must fix

  • All Motoko examples import mo:core/Random but use Random.Finite and f.byte(): These APIs do not exist in mo:core/Random. The Random.Finite class and its byte(), coin(), range() methods only exist in mo:base/Random. Verified by checking .sources/motoko-core/ — no Finite type in Random.mo. The correct import for all code using the Finite pattern is import Random "mo:base/Random". The random_maze example in .sources/examples/ uses import Random "mo:base/Random" — that's the authoritative reference. All Motoko examples using Random.Finite must change their import from mo:core/Random to mo:base/Random.

  • Buffer "mo:core/Buffer" import in rollMultipleDice does not exist: The mo:core package (motoko-core) does not contain a Buffer module. Buffer is in mo:base only. Importing Buffer "mo:core/Buffer" will fail to compile. Fix: change to import Buffer "mo:base/Buffer" (or rewrite the example to avoid Buffer entirely, using an array accumulator instead).

  • Rust tuple destructuring is wrong for the new API: All Rust examples use let (random_bytes,) = ic_cdk::management_canister::raw_rand().await... — this tuple pattern was the old API signature (ic_cdk::api::management_canister::main::raw_rand() returned CallResult<(Vec<u8>,)>). The current ic_cdk::management_canister::raw_rand() returns CallResult<Vec<u8>> (not a tuple). The tuple destructuring let (random_bytes,) = ... will not compile. Fix: let random_bytes = ic_cdk::management_canister::raw_rand().await.expect("raw_rand failed");

Suggestions

  • Modulo bias unaddressed: The range() method is presented as the idiomatic way to get a bounded random value, but a note that n % range introduces modulo bias is absent. A short callout ("use Random.range(n) rather than % n") would help developers avoid a subtle correctness issue.

  • Section title "Using getrandom" is misleading: The getrandom crate section describes calling raw_rand directly from Rust with no Motoko counterpart. The heading implies the standard getrandom interface, but the code is a direct management canister call. A more accurate title would be "Raw bytes (Rust)" or "Direct raw_rand call".

  • <!-- Source unavailable --> HTML comment should be removed: Internal HTML comments should not appear in published docs. This is an authoring artefact and should be stripped before merge.

  • Security section duplicates earlier content: The "Security considerations" section at the end repeats the bias and reseed points already made inline. Consider consolidating into a single location.

Verified

  • ic_cdk::management_canister::raw_rand function name verified in .sources/cdk-rs/ic-cdk/src/management_canister.rs
  • Random.Finite, f.byte(), f.coin(), f.range() confirmed in .sources/motoko-core/ — only in mo:base/Random, not mo:core/Random ✓ (bug confirmed)
  • Buffer module absence in motoko-core confirmed ✓ (bug confirmed)

@marc0olo
Copy link
Copy Markdown
Member Author

regarding mo:core and mo:base -> please read .sources/motoko/doc/md/12-base-core-migration.md

we don't want to reference mo:base usage anywhere in the docs and use mo:core instead

@marc0olo
Copy link
Copy Markdown
Member Author

Correction to my previous review

I made an error in the review posted earlier — please disregard the following items:

  • mo:core/Randommo:base/Random — this was wrong. mo:core is the correct library; mo:base is the legacy library being replaced. See the migration guide.
  • Add mo:base/Buffer — also wrong. Buffer is removed in mo:core; the replacement is mo:core/List. The mo:base import would be a regression.

The Rust tuple destructuring item in the review may still be valid — that one is independent of the mo:base/mo:core issue and should be verified against the actual code.

Apologies for the incorrect feedback.

marc0olo added a commit that referenced this pull request Apr 16, 2026
@marc0olo
Copy link
Copy Markdown
Member Author

Feedback addressed:

  • Fixed Rust tuple destructuring for raw_rand in all 5 call sites: let (random_bytes,) =let random_bytes = (current API returns CallResult<Vec<u8>>, not a tuple)
  • Fixed Motoko rollMultipleDice example: import Buffer "mo:core/Buffer" does not exist in mo:core; replaced with import List "mo:core/List" and updated all usages (Buffer.BufferList.empty, buf.addList.add, buf.sizeList.size, Buffer.toArrayList.toArray)
  • Removed <!-- Source unavailable --> authoring artifact comment
  • Renamed "Using getrandom in Rust" section → "Seeding a PRNG from raw_rand (Rust)" — the section shows a direct management canister call, not the standard getrandom interface

Note: mo:core imports for Random.Finite, f.byte(), etc. were intentionally kept as-is — mo:core is the correct library; mo:base is the legacy library being replaced.

Covers raw_rand API, Motoko and Rust implementations, range generation,
winner selection, getrandom/rand PRNG seeding, security considerations,
and the random_maze example.
@marc0olo marc0olo force-pushed the docs/guides-backends-randomness branch from 5a97c2f to 635058e Compare April 16, 2026 12:38
@marc0olo
Copy link
Copy Markdown
Member Author

Feedback addressed:

  • Rewrote all three Motoko examples (rollDie, rollMultipleDice, pickWinner) to remove the non-existent mo:core/Random.Finite API
  • New pattern: Random.blob()Blob.toArray(entropy) → direct array index (bytes[0], bytes[i]) instead of Random.Finite(entropy) + f.byte() switch
  • Added import Blob "mo:core/Blob" to each Motoko example
  • Updated prose descriptions to match the new pattern (no more references to "Finite class" or "consuming bytes incrementally")
  • Updated random_maze section note: clarified the upstream example predates mo:core and uses mo:base/Random.Finite; points readers to the guide's patterns instead
  • Updated <!-- Upstream: --> to add caffeinelabs/motoko-core — src/Random.mo

@marc0olo marc0olo merged commit 5388ae3 into main Apr 16, 2026
1 check passed
@marc0olo marc0olo deleted the docs/guides-backends-randomness branch April 16, 2026 13:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant